package ru.amse.podolsky.fractalarch.io;

/**
 *
 * @author Алексей
 */

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import ru.amse.podolsky.fractalarch.model.*;
import java.io.*;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

public class BitArchivIO implements IArchivIO{
    private final int numberOfDomenParams = 6;
    private final byte bitPerByte = 8;
    private final byte bitPerInt = 32;
    private final byte affinNumberLength = 3;

    private String path;

    public BitArchivIO(String s){
        this.path = s;
    }

    public void write(DomenParams[] params,int numberOfDomens, int middleColor, int width, int height, String step) throws IOException {
        //Побайтовая запись
        DataOutputStream output = new DataOutputStream(new FileOutputStream(this.path));

        //Записывается заголовок
        //Оптимизировать по width  и  height - не сейчас
        output.writeInt(numberOfDomens);
        output.writeByte((byte) middleColor);
        output.writeInt(width);
        output.writeInt(height);
        output.writeByte(toByte(step));

        //Записывается информация о доменах
        //Проблема с картинками меньше 8х8 
        
        byte buffer = shiftWrite(output, params[0], 0, (byte)0);
        for(int i = 1; i < params.length; ++i){
            buffer = shiftWrite(output, params[i], toShift(i % 8), buffer);
        }
        if(buffer != 0){
            output.write(buffer);
        }
        output.close();

        zipArchivationCompression(this.path);
    }

    public int[][] read() throws IOException{
        try {
            zipArchivationDecompression(this.path);
        } catch (DataFormatException ex) {
            Logger.getLogger(BitArchivIO.class.getName()).log(Level.SEVERE, null, ex);
        }

        DataInputStream input = new DataInputStream(new FileInputStream(this.path));

        //Читаю заголовок архивного файла

        int  param = input.readInt();
        
        int[][] out = new int [param+1][this.numberOfDomenParams];
        
        out[0][0] = param;
        param = 0;

        param = input.readUnsignedByte();
        out[0][1] = param;
        
        param = input.readInt();
        out[0][2] = param;
        param = 0;
        
        param = input.readInt();
        out[0][3] = param;
        param = 0;
        
        param = input.readUnsignedByte();
        out[0][4] = param;
        byte buffer = 0;

        out[0][5] = 1;

        //Читаю информацию о доменах
        int domen = 1;
        int shift = 0;
        while(domen < out[0][0]){
            switch((domen - 1) % 8){
                case(0):{shift = 0; break;}
                case(1):{shift = 5; break;}
                case(2):{shift = 2; break;}
                case(3):{shift = 7; break;}
                case(4):{shift = 4; break;}
                case(5):{shift = 1; break;}
                case(6):{shift = 6; break;}
                case(7):{shift = 3; break;}
            }

            buffer = shiftRead(input, out, shift, buffer, domen);

            ++domen;
        }
        

        input.close();

        zipArchivationCompression(this.path);

        return out;
    }

    //Пока не используется, вообще - автоматической формирование имени архива из
    //имени исходного изображения.

    public String archivName(String input){
        int length = input.length();
        char[] pathwrite = new char[length];
        for(int i = 0; i < length - 3; ++i){
            pathwrite[i] = input.charAt(i);
        }
        pathwrite[length-3] = 'f';
        pathwrite[length-2] = 'a';
        pathwrite[length-1] = 'h';
        return pathwrite.toString();
    }

    private byte toByte(String step){

        byte b = 0;

        switch(step.charAt(0)){
            case('0'):{b = 0; break;}
            case('1'):{b = 1; break;}
            case('2'):{b = 2; break;}
            case('3'):{b = 3; break;}
            case('4'):{b = 4; break;}
            case('5'):{b = 5; break;}
            case('6'):{b = 6; break;}
            case('7'):{b = 7; break;}
            case('8'):{b = 8; break;}
            case('9'):{b = 9; break;}
        }

        if(step.length() > 1){
            switch(step.charAt(1)){
                case('0'):{b = (byte)(b * 10 + 0); break;}
                case('1'):{b = (byte)(b * 10 + 1); break;}
                case('2'):{b = (byte)(b * 10 + 2); break;}
                case('3'):{b = (byte)(b * 10 + 3); break;}
                case('4'):{b = (byte)(b * 10 + 4); break;}
                case('5'):{b = (byte)(b * 10 + 5); break;}
                case('6'):{b = (byte)(b * 10 + 6); break;}
            }
        }

        return b;
    }

    private byte toShift(int num){
        switch(num){
            case(0):{return 0;}
            case(1):{return 3;}
            case(2):{return 6;}
            case(3):{return 1;}
            case(4):{return 4;}
            case(5):{return 7;}
            case(6):{return 2;}
            case(7):{return 5;}
        }
        return 0;
    }

    private byte shiftWrite(DataOutputStream output, DomenParams param, int shift, byte oldBuff) throws IOException{

        byte buffer = oldBuff;
        buffer = (byte) ((byte) ((byte) (param.sameBlockNumber >> 24) >> shift) | buffer);
        output.write(buffer);
        buffer = 0;
        output.write((byte) (param.sameBlockNumber >> (shift + 16)));
        output.write((byte) (param.sameBlockNumber >> (shift + 8)));
        output.write((byte) (param.sameBlockNumber >> (shift)));
        buffer = (byte) (param.sameBlockNumber << (bitPerByte - shift));
        buffer = (byte) (buffer | ((byte) param.shift >> shift));
        output.write(buffer);
        buffer = (byte) ((byte) param.shift << (bitPerByte - shift));
        if(shift == 6 || shift == 7 || shift == 5){
            buffer = (byte) (buffer | ((byte) param.numberOfConversion >> (affinNumberLength - bitPerByte + shift)));
            output.write(buffer);
            buffer =  (byte) ((byte) param.numberOfConversion << (2 * bitPerByte - affinNumberLength - shift));
        }else{
            buffer = (byte) (buffer | ((byte) param.numberOfConversion << (bitPerByte - affinNumberLength - shift)));
        }

        return buffer;
    }
    
    private byte shiftRead(DataInputStream input, int [][] out, int shift, byte oldBuff, int domenNumber) throws IOException{
        //TODO:Передалть для int тоже unsigned
        int bufferInt1 = input.readInt();
        int bufferInt2 = oldBuff;
        bufferInt2 = (bufferInt1 >> shift) | (bufferInt2 << 24);
        out[domenNumber][0] = bufferInt2;
                
        byte bufferByte1 = (byte)(bufferInt1 << (bitPerByte - shift));
        bufferInt2 = input.readUnsignedByte();
        byte bufferByte2 = (byte) (bufferInt2 >> shift);
        
        bufferByte1 = (byte) (bufferByte1 | (bufferByte2));
        out[domenNumber][1] = bufferByte1;

        bufferByte2 = (byte) (bufferInt2 << (bitPerByte - shift));
        if(shift == 0 || shift == 2 || shift == 1){
            bufferInt1 = input.readUnsignedByte();
            bufferByte1 = (byte) (bufferInt1 >> (bitPerByte + (shift - affinNumberLength)));
            bufferByte2 = (byte) ((bufferByte2 >> (bitPerByte - affinNumberLength)) | (bufferByte1));
            out[domenNumber][2] = bufferByte2;
            bufferByte1 = (byte) (bufferInt1 << (affinNumberLength - shift));
        }else{
            bufferByte1 = (byte) (bufferByte2 >> (bitPerByte - affinNumberLength));
            out[domenNumber][2] = bufferByte1;
            bufferByte1 = (byte) (bufferByte2 << affinNumberLength);
        }
        return bufferByte1;
    }

    // <editor-fold defaultstate="collapsed" desc="ZIP">
    private void zipArchivationCompression(String path) throws FileNotFoundException, IOException{
        FileInputStream input = new FileInputStream(path);
        int length = input.available();
        byte[] inputArr = new byte[length];
        byte[] outputArr = new byte[length + length/10];
        input.read(inputArr);
        Deflater compresser = new Deflater();
        compresser.setInput(inputArr);
        compresser.finish();
        int compressedDataLength = compresser.deflate(outputArr);
        input.close();
        DataOutputStream output = new DataOutputStream(new FileOutputStream(path));
        output.writeByte(IParams.BIT_ARCHIV_FLAG);
        output.writeInt(length);
        output.write(outputArr, 0, compressedDataLength);
        output.close();
    }

    private void zipArchivationDecompression(String path) throws FileNotFoundException, IOException, DataFormatException{
        DataInputStream input = new DataInputStream(new FileInputStream(path));
        //Пропускаем BIT_ARCHIV_FLAG
        input.readByte();
        
        int length = input.readInt();
        int inputLength = input.available();
        byte[] inputArr = new byte[inputLength];
        byte[] outputArr = new byte[length];
        input.read(inputArr);
        Inflater decompresser = new Inflater();
        decompresser.setInput(inputArr);
        decompresser.inflate(outputArr);
        input.close();
        FileOutputStream output = new FileOutputStream(path);
        output.write(outputArr);
        output.close();
    }
    // </editor-fold>
}
